class StatisticSDK {
  constructor(productID) {
    this.productID = productID;
    // 初始化自动调用性能上报
    this.initPerformance();
    // 初始化错误监控
    this.initError();
  }

  send(baseURL, query = {}) {
    query.productID = this.productID;
    let queryStr = Object.entries(query)
      .map(([key, value]) => `${key}=${value}`)
      .join('&');

    // img标签的优点是不需要将其append到文档，只需设置src属性便能成功发起请求
    // 通常请求的这个url会是一张1X1px的GIF图片,原因：
    // 1同样大小，不同格式的的图片中GIF大小是最小的，所以选择返回一张GIF，这样对性能的损耗更小
    // 2如果返回204，会走到img的onerror事件，并抛出一个全局错误；如果返回200和一个空对象会有一个CORB的告警
    // 3有一些埋点需要真实的加到页面上，比如垃圾邮件的发送者会添加这样一个隐藏标志来验证邮件是否被打开，如果返回204或者是200空对象会导致一个明显图片占位符
    let img = new Image();
    img.src = `${baseURL}?${queryStr}`;
  }

  // 性能上报
  initPerformance() {
    // performance.timing参数解释：
    // navigationStart 页面开始加载
    // domLoading DOM开始解析时间(FirstPaint)
    // domContentLoadedEventEnd 当所有需要立即执行的脚本已经被执行(不论执行顺序)时的时间(DOMContentEventLoad)
    // loadEventEnd load事件结束时的时间
    // 1 页面首次渲染时间: FP(FirstPaint)=domLoading-navigationStart;
    // 2 DOM加载完成: DCL(domContentLoadedEventEnd)=domContentLoadedEventEnd-navigationStart;
    // 3 图片、样式等外链资源加载完成: L(Load)=loadEventEnd-navigationStart;
    let performanceURL = 'http://performance/';
    this.send(performanceURL, performance.timing);
  }

  // 自定义错误上报
  error(err, etraInfo = {}) {
    const errorURL = 'http://error/';
    const { message, stack } = err;
    this.send(errorURL, { message, stack, ...etraInfo });
  }

  // 初始化错误监控
  initError() {
    window.addEventListener('error', (event) => {
      this.error(error);
    });
    // Promise内部抛出的错误是无法被error捕获到的，这时需要用unhandledrejection事件。
    window.addEventListener('unhandledrejection', (event) => {
      this.error(new Error(event.reason), { type: 'unhandledrejection' });
    });
  }

  // 自定义事件
  event(key, val = {}) {
    let eventURL = 'http://demo/';
    this.send(eventURL, { event: key, ...val });
  }
  // pv曝光
  pv() {
    this.event('pv');
  }
}

// 全局初始化一次
window.insSDK = new StatisticSDK('uuid-12345');
<button
  onClick={() => {
    window.insSDK.event('click', 'confirm');
  }}
>
  确认
</button>;

// React的错误边界
// 使用一个带有特殊生命周期的类组件，用它把业务组件包裹起来。
// 两个生命周期是getDerivedStateFromError和componentDidCatch
// 定义错误边界
// class ErrorBoundary extends React.Component {
//   state = { error: null };
//   static getDerivedStateFromError(error) {
//     return { error };
//   }
//   componentDidCatch(error, errorInfo) {
//     // 调用我们实现的SDK实例
//     insSDK.error(error, errorInfo);
//   }
//   render() {
//     if (this.state.error) {
//       return <h2>Something went wrong.</h2>;
//     }
//     return this.props.children;
//   }
// }
// <ErrorBoundary>
//   <BuggyCounter />
// </ErrorBoundary>

// Vue的错误边界
// Vue.component('ErrorBoundary', {
//   data: () => ({ error: null }),
//   errorCaptured (err, vm, info) {
//     this.error = `${err.stack}\n\nfound in ${info} of component`
//     // 调用我们的SDK，上报错误信息
//     insSDK.error(err,info)
//     return false
//   },
//   render (h) {
//     if (this.error) {
//       return h('pre', { style: { color: 'red' }}, this.error)
//     }
//     return this.$slots.default[0]
//   }
// })
// <error-boundary>
//   <buggy-counter />
// </error-boundary>
